/*
 * Copyright (C) 2011 Markus Junginger, greenrobot (http://greenrobot.de)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mlizhi.base.dao.test;

import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import com.mlizhi.base.dao.AbstractDao;
import com.mlizhi.base.dao.Property;
import com.mlizhi.base.dao.internal.SqlUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Default tests for single-PK entities.
 *
 * @param <D> DAO class
 * @param <T> Entity type of the DAO
 * @param <K> Key type of the DAO
 * @author Markus
 */
public abstract class AbstractDaoTestSinglePk<D extends AbstractDao<T, K>, T, K> extends AbstractDaoTest<D, T, K> {

protected Set<K> usedPks;
private Property pkColumn;

public AbstractDaoTestSinglePk(Class<D> daoClass) {
super(daoClass);
usedPks = new HashSet<K>();
}

@Override
protected void setUp() throws Exception {
super.setUp();
Property[] columns = daoAccess.getProperties();
for (Property column : columns) {
if (column.primaryKey) {
if (pkColumn != null) {
throw new RuntimeException("Test does not work with multiple PK columns");
}
pkColumn = column;
}
}
if (pkColumn == null) {
throw new RuntimeException("Test does not work without a PK column");
}
}

public void testInsertAndLoad() {
K pk = nextPk();
T entity = createEntity(pk);
dao.insert(entity);
assertEquals(pk, daoAccess.getKey(entity));
T entity2 = dao.load(pk);
assertNotNull(entity2);
assertEquals(daoAccess.getKey(entity), daoAccess.getKey(entity2));
}

public void testInsertInTx() {
dao.deleteAll();
List<T> list = new ArrayList<T>();
for (int i = 0; i < 20; i++) {
list.add(createEntityWithRandomPk());
}
dao.insertInTx(list);
assertEquals(list.size(), dao.count());
}

public void testCount() {
dao.deleteAll();
assertEquals(0, dao.count());
dao.insert(createEntityWithRandomPk());
assertEquals(1, dao.count());
dao.insert(createEntityWithRandomPk());
assertEquals(2, dao.count());
}

public void testInsertTwice() {
K pk = nextPk();
T entity = createEntity(pk);
dao.insert(entity);
try {
dao.insert(entity);
fail("Inserting twice should not work");
} catch (SQLException expected) {
// OK
}
}

public void testInsertOrReplaceTwice() {
T entity = createEntityWithRandomPk();
long rowId1 = dao.insert(entity);
long rowId2 = dao.insertOrReplace(entity);
if (dao.getPkProperty().type == Long.class) {
assertEquals(rowId1, rowId2);
}
}

public void testInsertOrReplaceInTx() {
dao.deleteAll();
List<T> listPartial = new ArrayList<T>();
List<T> listAll = new ArrayList<T>();
for (int i = 0; i < 20; i++) {
T entity = createEntityWithRandomPk();
if (i % 2 == 0) {
listPartial.add(entity);
}
listAll.add(entity);
}
dao.insertOrReplaceInTx(listPartial);
dao.insertOrReplaceInTx(listAll);
assertEquals(listAll.size(), dao.count());
}

public void testDelete() {
K pk = nextPk();
dao.deleteByKey(pk);
T entity = createEntity(pk);
dao.insert(entity);
assertNotNull(dao.load(pk));
dao.deleteByKey(pk);
assertNull(dao.load(pk));
}

public void testDeleteAll() {
List<T> entityList = new ArrayList<T>();
for (int i = 0; i < 10; i++) {
T entity = createEntityWithRandomPk();
entityList.add(entity);
}
dao.insertInTx(entityList);
dao.deleteAll();
assertEquals(0, dao.count());
for (T entity : entityList) {
K key = daoAccess.getKey(entity);
assertNotNull(key);
assertNull(dao.load(key));
}
}

public void testDeleteInTx() {
List<T> entityList = new ArrayList<T>();
for (int i = 0; i < 10; i++) {
T entity = createEntityWithRandomPk();
entityList.add(entity);
}
dao.insertInTx(entityList);
List<T> entitiesToDelete = new ArrayList<T>();
entitiesToDelete.add(entityList.get(0));
entitiesToDelete.add(entityList.get(3));
entitiesToDelete.add(entityList.get(4));
entitiesToDelete.add(entityList.get(8));
dao.deleteInTx(entitiesToDelete);
assertEquals(entityList.size() - entitiesToDelete.size(), dao.count());
for (T deletedEntity : entitiesToDelete) {
K key = daoAccess.getKey(deletedEntity);
assertNotNull(key);
assertNull(dao.load(key));
}
}

public void testDeleteByKeyInTx() {
List<T> entityList = new ArrayList<T>();
for (int i = 0; i < 10; i++) {
T entity = createEntityWithRandomPk();
entityList.add(entity);
}
dao.insertInTx(entityList);
List<K> keysToDelete = new ArrayList<K>();
keysToDelete.add(daoAccess.getKey(entityList.get(0)));
keysToDelete.add(daoAccess.getKey(entityList.get(3)));
keysToDelete.add(daoAccess.getKey(entityList.get(4)));
keysToDelete.add(daoAccess.getKey(entityList.get(8)));
dao.deleteByKeyInTx(keysToDelete);
assertEquals(entityList.size() - keysToDelete.size(), dao.count());
for (K key : keysToDelete) {
assertNotNull(key);
assertNull(dao.load(key));
}
}

public void testRowId() {
T entity1 = createEntityWithRandomPk();
T entity2 = createEntityWithRandomPk();
long rowId1 = dao.insert(entity1);
long rowId2 = dao.insert(entity2);
assertTrue(rowId1 != rowId2);
}

public void testLoadAll() {
dao.deleteAll();
List<T> list = new ArrayList<T>();
for (int i = 0; i < 15; i++) {
T entity = createEntity(nextPk());
list.add(entity);
}
dao.insertInTx(list);
List<T> loaded = dao.loadAll();
assertEquals(list.size(), loaded.size());
}

public void testQuery() {
dao.insert(createEntityWithRandomPk());
K pkForQuery = nextPk();
dao.insert(createEntity(pkForQuery));
dao.insert(createEntityWithRandomPk());

String where = "WHERE " + dao.getPkColumns()[0] + "=?";
List<T> list = dao.queryRaw(where, pkForQuery.toString());
assertEquals(1, list.size());
assertEquals(pkForQuery, daoAccess.getKey(list.get(0)));
}

public void testUpdate() {
dao.deleteAll();
T entity = createEntityWithRandomPk();
dao.insert(entity);
dao.update(entity);
assertEquals(1, dao.count());
}

public void testReadWithOffset() {
K pk = nextPk();
T entity = createEntity(pk);
dao.insert(entity);

Cursor cursor = queryWithDummyColumnsInFront(5, "42", pk);
try {
T entity2 = daoAccess.readEntity(cursor, 5);
assertEquals(pk, daoAccess.getKey(entity2));
} finally {
cursor.close();
}
}

public void testLoadPkWithOffset() {
runLoadPkTest(10);
}

public void testLoadPk() {
runLoadPkTest(0);
}

protected void runLoadPkTest(int offset) {
K pk = nextPk();
T entity = createEntity(pk);
dao.insert(entity);

Cursor cursor = queryWithDummyColumnsInFront(offset, "42", pk);
try {
K pk2 = daoAccess.readKey(cursor, offset);
assertEquals(pk, pk2);
} finally {
cursor.close();
}
}

protected Cursor queryWithDummyColumnsInFront(int dummyCount, String valueForColumn, K pk) {
StringBuilder builder = new StringBuilder("SELECT ");
for (int i = 0; i < dummyCount; i++) {
builder.append(valueForColumn).append(",");
}
SqlUtils.appendColumns(builder, "T", dao.getAllColumns()).append(" FROM ");
builder.append(dao.getTablename()).append(" T");
if (pk != null) {
builder.append(" WHERE ");

assertEquals(1, dao.getPkColumns().length);
builder.append(dao.getPkColumns()[0]).append("=");
DatabaseUtils.appendValueToSql(builder, pk);
}

String select = builder.toString();
Cursor cursor = db.rawQuery(select, null);
assertTrue(cursor.moveToFirst());
try {
for (int i = 0; i < dummyCount; i++) {
assertEquals(valueForColumn, cursor.getString(i));
}
if (pk != null) {
assertEquals(1, cursor.getCount());
}
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
return cursor;
}

/** Provides a collision free PK () not returned before in the current test. */
protected K nextPk() {
for (int i = 0; i < 100000; i++) {
K pk = createRandomPk();
if (usedPks.add(pk)) {
return pk;
}
}
throw new IllegalStateException("Could not find a new PK");
}

protected T createEntityWithRandomPk() {
return createEntity(nextPk());
}

/** K does not have to be collision free, check nextPk for collision free PKs. */
protected abstract K createRandomPk();

/**
 * Creates an insertable entity. If the given key is null, but the entity's PK is not null the method must return
 * null.
 */
protected abstract T createEntity(K key);

}